﻿using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;

namespace Test_API
{
    abstract class GenericModuleTest : InitAndPingTest
    {
        protected string viewAllMethod;
        protected string viewSingleItemMethod;
        protected string deleteMethod;
        protected string cid;
        protected List<int> validItemIDs;
        protected string projectFolder;

        public GenericModuleTest(string accessToken)
            : base(accessToken)
        {
            this.validItemIDs = new List<int>();
        }
        protected string genericQueryStringGenerate() { return this.genericQueryStringGenerate(this.validItemIDs.randomElement()); }
        abstract protected string genericQueryStringGenerate(int validItemId);
        abstract protected Dictionary<string, object> genericPostDataSetGenerate();

        protected string uploadMethod;

        #region ViewAll, ViewSingle
        protected List<LogEntry> viewAll(string itemIdAlias = "itemId")
        {
            List<LogEntry> entries = new List<LogEntry>();

            LogEntry t1 = new LogEntry(this.moduleName, true);
            HTTP.execute(HTTP.RequestType.GET, this.rootUrl, this.viewAllMethod, this.genericQueryStringGenerate(), null, ref t1);
            entries.Add(t1);
            try
            {
                JObject receivedJson = JObject.Parse(t1.receivedData);
                if ((string)receivedJson.SelectToken("dataSet[0]." + itemIdAlias) != null)
                {
                    this.validItemIDs.Clear();
                    var postTitles =
                        from p in receivedJson["dataSet"]
                        select (int)p[itemIdAlias];
                    this.validItemIDs.AddRange(postTitles);
                }
            }
            catch { }
            return entries;
        }

        protected List<LogEntry> viewSingle()
        {
            List<LogEntry> entries = new List<LogEntry>();
            LogEntry t1 = new LogEntry(this.moduleName, true); // tN = truth value required test

            //Basic
            t1.inputs = this.genericQueryStringGenerate();
            HTTP.execute(HTTP.RequestType.GET, this.rootUrl, this.viewSingleItemMethod, t1.inputs, null, ref t1);
            entries.Add(t1);

            //Empty accesstoken
            LogEntry f2 = new LogEntry(this.moduleName, false);
            string temp = this.accesstoken;
            this.accesstoken = null;

            HTTP.execute(HTTP.RequestType.GET, this.rootUrl, this.viewSingleItemMethod, this.genericQueryStringGenerate(), null, ref f2);
            entries.Add(f2);
            this.accesstoken = temp;

            return entries;
        }
        #endregion

        #region Download File
        /// <summary>
        /// Downloads a file from server
        /// </summary>
        /// <remarks>Default: ("dataSet[0].fileInformation.downloadUrl","dataSet[0].fileInformation.fileName")</remarks>
        /// <returns></returns>
        protected List<LogEntry> downloadFile()
        {
            return this.downloadFile("dataSet[0].fileInformation.downloadUrl", "dataSet[0].fileInformation.fileName");
        }
        protected List<LogEntry> downloadFile(string downloadUrlPattern, string fileNamePattern)
        {
            int itemid = this.validItemIDs.randomElement();
            LogEntry t1 = new LogEntry(this.moduleName, true);
            t1.methodName = "downloadFile";

            if (itemid <= 0)
            {
                t1.verdict = true;
                t1.receivedData = "No validItemID exists (to Download)";
                return new List<LogEntry>() { t1 };
            }

            string downloadUrl, fileName, filePath;
            try
            {
                LogEntry tempMetadataFetch = new LogEntry(this.moduleName, true);
                HTTP.execute(HTTP.RequestType.GET, this.rootUrl, this.viewAllMethod, this.genericQueryStringGenerate(), null, ref tempMetadataFetch);
                JObject receivedJson = JObject.Parse(tempMetadataFetch.receivedData);

                fileName = (string)receivedJson.SelectToken(fileNamePattern);
                downloadUrl = (string)receivedJson.SelectToken(downloadUrlPattern);
                if (fileName == null || downloadUrl == null)
                {
                    var allElement = 
                       (JArray) receivedJson.SelectToken("dataSet");
                    var firstElement = allElement.ToList().Where(x => ((bool)x["isDirectory"]) == false).First();
                    fileName = (string)firstElement.SelectToken("fileInformation.fileName");
                    downloadUrl = (string)firstElement.SelectToken("fileInformation.downloadUrl");
                }
                filePath = System.IO.Path.Combine(this.projectFolder, fileName);
                if (File.Exists(filePath))
                {
                    filePath = System.IO.Path.Combine(this.projectFolder, ExtenstionMethods.randomNumber(1000, 9999) + "_" + fileName);
                }

                downloadUrl = new Dictionary<string, string>() { { "cid", this.cid }, { "accesstoken", this.accesstoken }, { "downloadUrl", downloadUrl } }.buildQueryString();

                WebClient wc = new WebClient();
                t1.inputs = this.rootUrl + String.Format("downloadFile/{0}?", fileName) + downloadUrl;
                long startTime = DateTime.Now.Ticks;
                using (MemoryStream stream = new MemoryStream(wc.DownloadData(t1.inputs)))
                {
                    stream.WriteTo(File.Open(filePath, FileMode.OpenOrCreate));
                    t1.receivedData = String.Format("File downloaded: {0}", filePath);
                    t1.verdict = true;
                    t1.elapsedTime = (DateTime.Now.Ticks - startTime) / TimeSpan.TicksPerMillisecond;
                }
            }
            catch (Exception e)
            {
                t1.verdict = false;
                t1.receivedData = e.Message + e.StackTrace;
            }

            return new List<LogEntry>() { t1 };
        }
        #endregion

        #region Delete
        protected List<LogEntry> delete()
        {
            List<LogEntry> entries = new List<LogEntry>();
            int testId;
            LogEntry t1 = new LogEntry(this.moduleName, true);
            if (this.validItemIDs.Count == 0)
            {
                t1.verdict = true;
                t1.methodName = "deleteXModule";
                t1.receivedData = "No validItemID exists (to Delete)";
                entries.Add(t1);
                return entries;
            }
            testId = this.validItemIDs.randomElement();
            HTTP.execute(HTTP.RequestType.GET, this.rootUrl, this.deleteMethod, this.genericQueryStringGenerate(testId), null, ref t1);
            entries.Add(t1);

            //try to delete again
            LogEntry f2 = new LogEntry(this.moduleName, false);
            HTTP.execute(HTTP.RequestType.GET, this.rootUrl, this.deleteMethod, this.genericQueryStringGenerate(testId), null, ref f2);
            entries.Add(f2);

            return entries;
        }
        #endregion

        #region Create Folder
        protected List<LogEntry> createFolder(int moduleNumber)
        {
            string queryString = new Dictionary<string, string>() { { "accessToken", this.accesstoken }, { "cid", this.cid }, { "moduleNumber", moduleNumber.ToString() }, { "desiredFolderName", Library.getWord() } }.buildQueryString() + "sourceDirectory=";

            LogEntry t1 = new LogEntry(this.moduleName, true);
            LogEntry t2 = new LogEntry(this.moduleName, true);
            LogEntry t3 = new LogEntry(this.moduleName, false);
            List<LogEntry> resultEntries = new List<LogEntry>();
            string folderCreateMethodName = "createFolder";

            // Create folder in root folder
            HTTP.execute(HTTP.RequestType.GET, this.rootUrl, folderCreateMethodName, queryString, null, ref t1);
            resultEntries.Add(t1);
            bool folderExists = API_VERDICT.checkFolderIsCreated(ref t1, "Parent folder not created");
            if (folderExists == false)
            {
                return resultEntries;
            }
            // Create folder in the parent/newly created folder
            Dictionary<string, object> htmlAttributes = JsonConvert.DeserializeObject<Dictionary<string, object>>(t1.receivedData);
            string parentFolderUrl = (string)htmlAttributes["selfUrl"];
            HTTP.execute(HTTP.RequestType.GET, this.rootUrl, folderCreateMethodName, queryString + parentFolderUrl, null, ref t2);
            API_VERDICT.checkFolderIsCreated(ref t2, "Child folder not created");
            resultEntries.Add(t2);

            // Upload a file in the parent(newly) created folder
            string[] uploadFileLocation = Directory.GetFiles(this.projectFolder);
            if (uploadFileLocation.Length > 0)
            {
                string randomFileLocation = uploadFileLocation.ToList().randomElement();
                resultEntries.AddRange(this.uploadFile(randomFileLocation, moduleNumber, this.uploadMethod, (string)htmlAttributes["selfUrl"]));
            }
            return resultEntries;
        }
        #endregion

        #region Upload File
        protected List<LogEntry> uploadFile(string localFileLocation, int whichModule, string uploadMethodName)
        {
            return this.uploadFile(localFileLocation, whichModule, uploadMethodName, null);
        }
        protected List<LogEntry> uploadFile(string localFileLocation, int whichModule, string uploadMethodName, string fixedServerFolderUrl)
        {
            localFileLocation = localFileLocation.Replace("/", "\\");
            string serverFolderUrl = String.Empty;
            string destinationFolderKeyName = "sourceDirectory";
            switch (whichModule)
            {
                case 0://learning materials
                case 1://media library
                case 2://shared documents
                case 3://peer review
                    break;
                case 4://announcement
                    destinationFolderKeyName = "attachmentDirectory"; break;
                case 6://assignment
                    destinationFolderKeyName = "solutionDirectory"; break;
                case 5://email
                    serverFolderUrl = String.Empty; break;
            }

            // Get a folder from server
            List<LogEntry> viewAllBeforeUpload = this.viewAll();
            LogEntry t1 = new LogEntry(this.moduleName, true);

            if (API_VERDICT.checkAtleasetOneElementIsPresent(viewAllBeforeUpload[0]))
            {
                JObject receivedJson = JObject.Parse(viewAllBeforeUpload[0].receivedData);
                JArray dataSet = (JArray)receivedJson["dataSet"];
                if (whichModule != 6)
                {
                    serverFolderUrl = (string)dataSet[ExtenstionMethods.randomNumber(0, dataSet.Count)][destinationFolderKeyName];
                }
                else
                {
                    JToken solution = (JToken)dataSet[ExtenstionMethods.randomNumber(0, dataSet.Count)]["solution"];
                    if (solution.Count() == 0)
                    {
                        t1.methodName = uploadMethodName;
                        t1.verdict = true;
                        t1.receivedData = "/* Solution not yet creted */";
                        return new List<LogEntry>() { t1 };
                    }
                    serverFolderUrl = (string)solution[destinationFolderKeyName];
                }
            }
            else
                serverFolderUrl = String.Empty;

            serverFolderUrl = fixedServerFolderUrl == null ? serverFolderUrl : fixedServerFolderUrl;

            // Do the upload
            Dictionary<string, string> queryString = new Dictionary<string, string>() { { destinationFolderKeyName, serverFolderUrl }, { "accessToken", this.accesstoken }, { "cid", this.cid } };
            try
            {
                byte[] data = File.ReadAllBytes(localFileLocation);
                string base64String = Convert.ToBase64String(data);

                HTTP.execute(HTTP.RequestType.UPLOAD, this.rootUrl, uploadMethodName, queryString.buildQueryString(), new Dictionary<string, object>() { { "stream", base64String }, { "fileName", new FileInfo(localFileLocation).Name } }, ref t1);

                t1.inputs = String.Format("Local file location: {1}; attempting to upload in serverFolder: {0}", serverFolderUrl, localFileLocation);
            }
            catch (Exception e)
            {
                t1.verdict = false;
                t1.receivedData = e.Message;
            }
            return new List<LogEntry>() { t1 };
        }
        #endregion
    }
}